/*
 * 
 * Copyright (C) 2010 Wilfried Elmenreich, Anita Sobe 
 * This file is part of VideoNetwork.
 * 
 * VideoNetwork is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 
 * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. 
 */
package proxy;

import java.util.ArrayList;

import simulation.Simulator;


public class ShortestPathRouting {
	ArrayList<ServerNode> route=new ArrayList<ServerNode>();
	private Simulator simulator;
	static final int maxHops=5;
	
	private boolean recurseSearchRoute(ServerNode currentNode, int remainingDepth, int id)
	{
		//was this node already visited?
		if (route.contains(currentNode)) return false;
		
		//add this node to the route
		route.add(currentNode);
		//have we found the unit?
		if (currentNode.searchPresentUnit(id) != null)
			return true;
		//maxDepth reached?
		if (remainingDepth==0) {
			route.remove(currentNode);
			return false;
		}
		//search the adjacent nodes
		for(Link li:currentNode.getLinks())
			if (recurseSearchRoute(li.getTargetNode(), remainingDepth-1, id)) return true;
		//this was a dead end, remove this node from the route
		route.remove(currentNode);
		return false;
	}
	
	private ArrayList<ServerNode> SearchRoute(ServerNode startingServer, int depth, int id)
	{
		route=new ArrayList<ServerNode>();
		if (recurseSearchRoute(startingServer, depth, id))
			return route;
		else
			return null;
	}
	
	void routeUnit(ArrayList<ServerNode> route, int id) {
		if (route.size()==1) return; //unit is already local
		String s, delim="";
		s="Routing u"+id;
		for(int i=route.size()-1; i>=0; i--) {
			s=s+delim+" S"+route.get(i).getId();
			delim=" ->";
		}
		simulator.log(s);
		//System.out.println(s);
		ServerNode lastServer=route.get(route.size()-1);
		boolean copy=false;
		UnitContainer uc=lastServer.searchPresentUnit(id);
		//if this unit is already routed somewhere, we must copy it
		if (uc.route != null)
			copy=true;
		//if this unit is pinned, we must copy it
		if (simulator.getSimTime() < lastServer.searchPresentUnit(id).lastUse())
			copy=true;
		
		ServerNode nextServer=route.get(route.size()-2);
		if (lastServer.moveUnitTo(uc.getVideoUnit(), lastServer.getLinkTo(nextServer), copy)) {
			route.remove(lastServer);
			if (route.size() >=2) nextServer.searchInIncomingUnits(id).route=route;
		}
	}
	
	double expectedTransferTime(ArrayList<ServerNode> route,int id) {
		double overallTime=0.0;
		for(int i=route.size()-1; i>0; i--) {
			Link li=route.get(i).getLinkTo(route.get(i-1));
			if (li==null) return 0.0;
			double occupiedTime=li.getOccupiedUntil()-simulator.getSimTime();
			if (occupiedTime < 0) occupiedTime=0;
			overallTime+=occupiedTime+VideoUnit.avgSize/li.getDatarate();
		}
		return overallTime;
	}
	
	public boolean fetchOnShortestRoute(Simulator simulator,ServerNode startingServer, int id) {
		//check if there is a fetch in progress for this id and this server
		if (startingServer.getFetchTimeOut().containsKey(id)) {
			if (simulator.getSimTime() < startingServer.getFetchTimeOut().get(id)) {
				return false;
			}
			else {
				//the registered fetch process has timed out
				startingServer.getFetchTimeOut().remove(id);
			}
		}
		
		ArrayList<ServerNode> route=null;
		
		this.simulator=simulator;
		for (int i=1; i<maxHops; i++) {
			route=SearchRoute(startingServer, i, id);
			if (route!=null) break;
		}
		if (route==null) {
			simulator.log("Searching for u"+id+" from S"+startingServer.getId()+" failed!");
			return false;
		}
		startingServer.getFetchTimeOut().put(id, expectedTransferTime(route,id));
		routeUnit(route,id);
		
		return true;
	}
}
